package cn.suimg.bilibili.util;

import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Map;
import java.util.Map.Entry;

/**
 * HttpClientUtil
 * http请求工具类(封装apache-commons版本!)
 */
public final class HttpClientUtil {

    /**
     * 异步回调类
	 *
     */
    public static class AysncCallback{

        /**
         * 仅获取输出结果
         */
        @FunctionalInterface
		public interface WithDataCallBack {
            void callback(String data);
        }

        /**
         * 获取输出结果和Cookie
         */
        @FunctionalInterface
        public interface WithDataAndCookieCallBack {
            void callback(String data, String cookie);
        }

        @FunctionalInterface
        public interface WithInputStreamCallback {
        	void callback(InputStream is);
		}
    }

	/**
	 * Client
	 */
	private HttpClient client;

	/**
	 * Method
	 */
	private HttpMethod method = null;

	/**
	 * Execed
	 */
	private Boolean execed = false;

	/**
	 * Logger
	 */
	private Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

	/**
	 * 构造方法
	 */
	public HttpClientUtil() {
		client = new HttpClient();
	}

	/**
	 *
	 * @param url
	 */
	public HttpClientUtil(String url){
		this();
		get(url);
	}

	/**
	 * 使用GET方法去请求
	 *
	 * @param url
	 * @return
	 */
	public HttpClientUtil get(String url, Map<String, Object> args) {
		return get(map2GetPairs(url, args));
	}

	/**
	 * 使用GET方法去请求
	 *
	 * @param url
	 * @return
	 */
	public HttpClientUtil get(String url) {
		if (method != null) {
			throw new RuntimeException();
		}
		logger.debug("GET:{}",url);
		method = new GetMethod(url);
		return this;
	}

	/**
	 * 直接获取响应结果
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static String getData(String url) {
		return new HttpClientUtil().get(url).exec().getResponseData();
	}

	/**
	 * 直接获取响应结果
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static String getData(String url,Map<String, Object> args) {
		return new HttpClientUtil().get(map2GetPairs(url, args)).exec().getResponseData();
	}

	/**
	 * 构件一个post方法
	 * @param url
	 * @return
	 */
	public static String postData(String url,Map<String,Object> args){
		return new HttpClientUtil().post(url,args).exec().getResponseData();
	}

	/**
	 * POST提交但是不POST数据
	 *
	 * @param url
	 * @return
	 */
	public HttpClientUtil post(String url) {
		if (method != null) {
			throw new RuntimeException();
		}
		logger.debug("POST:{}",url);
		method = new PostMethod(url);
		return this;
	}

	/**
	 * POST提交方式
	 *
	 * @param url
	 * @param args
	 * @return
	 */
	public HttpClientUtil post(String url, Map<String, Object> args) {
		post(url);
		logger.debug("POST DATA:{}",args);
		((PostMethod) method).setRequestBody(map2NameValuePairs(args));
		return this;
	}

    /**
     * POST提交方式
     * @param url
     * @param getArgs
     * @param postArgs
     * @return
     */
	public HttpClientUtil post(String url,Map<String,Object> getArgs,Map<String,Object> postArgs){
	    return post(map2GetPairs(url, getArgs),postArgs);
    }

	/**
	 * POST数据到payload域当中(仅限于POST方法之后)
	 *
	 * @param data
	 * @return
	 */
	public HttpClientUtil postEntity(String data) {
		try {
			RequestEntity entity = new StringRequestEntity(data, "application/json", "utf-8");
			logger.debug("POST BODY:{}",data);
			((PostMethod) method).setRequestEntity(entity);
		} catch (UnsupportedEncodingException e) {}
		return this;
	}

	/**
	 * 上传文件
	 * @param file
	 * @return
	 * @throws FileNotFoundException
	 */
	public HttpClientUtil postFile(File file) throws FileNotFoundException {
		Part[] parts = { new FilePart(file.getName(), file) };
		logger.debug("POST FILE:{}",file.getAbsolutePath());
		((PostMethod) method).setRequestEntity(new MultipartRequestEntity(parts, method.getParams()));
		return this;
	}

	/**
	 * 设置自定义请求头信息
	 * @param name
	 * @param value
	 * @return
	 */
	public HttpClientUtil setHearder(String name,String value){
		method.setRequestHeader(name,value);
		return this;
	}

	/**
	 * 添加Cookie
	 * @param cookies
	 * @return
	 */
	public HttpClientUtil setCookie(String cookies){
		method.addRequestHeader("Cookie",cookies);
		return this;
	}

    /**
     * 设置引用页
     * @param referer
     * @return
     */
	public HttpClientUtil setReferer(String referer) {
        method.addRequestHeader("Referer", referer);
        return this;
    }

	/**
	 * 添加自定义请求头信息
	 * @param name
	 * @param value
	 * @return
	 */
	public HttpClientUtil addHeader(String name,String value){
		method.addRequestHeader(name,value);
		return this;
	}

	/**
	 * 执行这个请求
	 *
	 * @return
	 * @throws HttpException
	 * @throws IOException
	 */
	public HttpClientUtil exec() {
		if (method == null || client == null || execed ) {
			throw new RuntimeException();
		}
		client.getParams().setContentCharset("UTF-8");
		try{
			client.executeMethod(method);
		}catch (IOException e){
			logger.error("exec request error:{}",e.toString());
		}
		execed = true;
		return this;
	}

    /**
     * 异步执行完之后获取data
     * @param callback
     */
	public void exec(AysncCallback.WithDataCallBack callback){
        client.getParams().setContentCharset("UTF-8");
        ThreadPoolUtil.submit(() -> {
            try{
                client.executeMethod(method);
                execed = true;
                callback.callback(getResponseData());
            }catch (Exception e){
                logger.error("aysnc error:{}",e.toString());
            }
        });
    }


    /**
     * 异步执行完之后获取data & cookie
     * @param callback
     */
    public void exec(AysncCallback.WithDataAndCookieCallBack callback){
        client.getParams().setContentCharset("UTF-8");
		ThreadPoolUtil.submit(() -> {
            try{
                client.executeMethod(method);
                execed = true;
                callback.callback(getResponseData(),getCookie());
            }catch (Exception e){
                logger.error("aysnc error:{}",e.toString());
            }
        });
    }

	/**
	 * 异步执行完之后获取data & cookie
	 * @param callback
	 */
	public void exec(AysncCallback.WithInputStreamCallback callback){
		client.getParams().setContentCharset("UTF-8");
		ThreadPoolUtil.submit(() -> {
			try{
				client.executeMethod(method);
				execed = true;
				callback.callback(getResponseStream());
			}catch (Exception e){
				logger.error("aysnc error:{}",e.toString());
			}
		});
	}

    /**
     * 获取响应中的Cookie值
     * @return
     */
    public String getCookie() {
        StringBuffer sb = new StringBuffer();
        Header[] headers = method.getResponseHeaders("Set-Cookie");
        for (Header header : headers) {
            sb.append(String.format("%s; ",header.getValue().split(";")[0]));
        }
        return sb.toString();
    }

	/**
	 * 获取响应的数据为字符串格式
	 *
	 * @return
	 * @throws IOException
	 */
	public String getResponseData() {
		if (!execed) {
			throw new RuntimeException();
		}
		try{
			return method.getResponseBodyAsString();
		}catch (IOException e){
			logger.error("get response data error:{}",e.toString());
		}
		return null;
	}

	/**
	 * 获取响应的数据为输入流格式
	 *
	 * @return
	 * @throws IOException
	 */
	public InputStream getResponseStream() {
		if (!execed) {
			throw new RuntimeException();
		}
		try{
			return method.getResponseBodyAsStream();
		}catch (IOException e){
			logger.error("get response stream error:{}",e.toString());
		}
		return null;
	}

	/**
	 * 把Map转换为POST可识别的格式
	 *
	 * @param map
	 * @return
	 */
	private NameValuePair[] map2NameValuePairs(Map<String, Object> map) {
		NameValuePair[] values = new NameValuePair[map.size()];
		int i = 0;
		for (Entry<String, Object> entry : map.entrySet()) {
			values[i++] = new NameValuePair(entry.getKey(), String.valueOf(entry.getValue()));
		}
		return values;
	}

	/**
	 * 把Map转换为Get可识别的格式
	 *
	 * @param url
	 * @param map
	 * @return
	 */
	private static String map2GetPairs(String url, Map<String, Object> map) {
		StringBuilder sb = new StringBuilder();
		map.forEach((k,v) -> sb.append(String.format("%s=%s&",k,v)));
		return url + (url.contains("?") ? "&" : "?") + sb.substring(0, sb.length() - 1);
	}
}
